Skip to content

ci(release): support release/* branches with version-comparison gating#3530

Draft
ericallam wants to merge 4 commits intomainfrom
chore/release-branch-flow
Draft

ci(release): support release/* branches with version-comparison gating#3530
ericallam wants to merge 4 commits intomainfrom
chore/release-branch-flow

Conversation

@ericallam
Copy link
Copy Markdown
Member

@ericallam ericallam commented May 6, 2026

Adds support for shipping a patch release from a release/<major>.<minor>.x branch alongside the existing main-only flow. Use this when main has unreleased changes you can't include in the next release — a customer hotfix on the current line, an emergency patch, etc.

When to use this

You're on 4.4.5. Main has merged work queued for 4.5.0 (a feature, a refactor — anything not ready to ship). A customer hits a bug. Normally you'd be stuck: shipping from main bundles the unreleased work; reverting on main is messy.

With this PR you can cut release/4.4.x, ship 4.4.6, and main stays untouched. The patch goes through the normal release pipeline — npm publish, Docker image, Helm chart, GitHub release, marketing-site changelog — and customers running npm install @trigger.dev/sdk get it.

If you don't need this, don't use it. Normal main releases work exactly as before.

How to ship a patch from a release branch

The flow has five logical steps. Two of them are automated by the existing release pipeline and you don't do anything for them — they're called out here so you understand what's happening.

1. Create the release branch from the last release tag

A release branch is a parallel timeline that starts from a known-released commit and accumulates patches independently of main.

You always cut from a tag (v4.4.5), not from main HEAD. The tag is immutable and points at exactly the code customers are running. Cutting from main HEAD would pull in any commits or pending changesets that have landed since the tag — defeating the purpose.

The branch name follows the pattern release/<major>.<minor>.x — one branch per minor line. release/4.4.x accumulates 4.4.6, 4.4.7, etc. — never a 4.5.x release.

git fetch --tags origin
git switch -c release/4.4.x v4.4.5
git push -u origin release/4.4.x

2. Apply the fix

The release branch represents v4.4.5 + the fix, and nothing else from main. Two ways to get the fix onto the branch:

  • Fix already merged to main: cherry-pick the fix commit. Use git cherry-pick <sha> for a single commit, git cherry-pick A..B for a range.
  • Fix doesn't exist yet: write it directly on the release branch. You'll port it to main later.

Either way, the result is the same: this branch's tip is v4.4.5 + the fix.

3. Add a changeset (and optionally a .server-changes/ entry)

Changesets is the system that tells the release pipeline what to bump. Without a changeset, the pipeline has no work to do — there's no version to publish. Each changeset is a markdown file in .changeset/ declaring which packages bump and by how much.

For a patch release, you typically want patch on the package(s) you fixed. The changesets fixed config will cascade the bump across the linked packages automatically, so you don't need to list every package.

If the fix is server-only (webapp behavior, not a published package), use .server-changes/ instead. Both formats are picked up by the pipeline; only .changeset/ ones bump versions.

pnpm run changeset:add
git add .changeset/*.md .server-changes/*.md
git commit -m "fix: <description>"
git push origin release/4.4.x

4. (Automated) Version PR opens

Pushing to a release/* branch triggers changesets-pr.yml, the same workflow that opens release PRs from main. It runs changeset version, applies the bumps, stamps Chart.yaml, consumes any .server-changes/, and opens a pull request titled chore: release v4.4.6 (the title comes from the post-process script — that's also where the version is computed and shown).

The PR's body now starts with a "Release prep" header containing:

  • the proposed version
  • the source branch
  • the current latest on npm
  • whether this release will take the latest floating tags ("yes" if the proposed version is higher than current latest; "no" otherwise — see "What gets published" below for what that means)

That header is the at-a-glance check that the release pipeline understood the situation correctly. Review the rest of the PR like any other release PR.

5. Merge the version PR → release ships

Merging triggers release.yml. From the release pipeline's point of view, a release-branch merge looks identical to a main merge — same publish steps, same Docker tags, same Helm chart push, same marketing-site dispatch. The only thing that changes is which floating tags the publish takes (see below).

6. Port the fix back to main

After the release ships, the fix exists on the release branch but not on main. If you don't bring it back, main's next release won't include the fix and the same bug will reappear.

You want to cherry-pick the fix commit only — not the version-bump commit. The version-bump commit is the changeset PR's auto-generated bump (it edits package.json, Chart.yaml, etc.) and cherry-picking it onto main would conflict with main's existing versions and delete the consumed changeset.

git switch main
git cherry-pick <fix-sha>           # NOT the version-bump commit
git push

Main's next release naturally rolls up the fix at whatever version main is on.

What gets published

A successful release-branch run publishes:

  • npm: @trigger.dev/{sdk,core,cli-v3,...}@4.4.6
  • Docker: ghcr.io/triggerdotdev/trigger.dev:v4.4.6 and ghcr.io/triggerdotdev/trigger.dev:release-4.4
  • Helm: chart at version 4.4.6
  • GitHub release: v4.4.6
  • Marketing-site changelog: dispatched with the new version

Whether it also takes the floating tags (npm latest, Docker :v4-beta, GitHub "Latest" badge, headline placement on the changelog) depends on whether 4.4.6 is higher than the current latest on npm:

Situation Floating tags
Main hasn't shipped past 4.4.5 (queued work unreleased) → 4.4.6 > 4.4.5 ✅ moves to 4.4.6
Main has shipped 4.6.04.4.6 < 4.6.0 (lagged hotfix) ❌ stays on 4.6.0

In the lagged case, 4.4.6 still publishes — but customers running npm install (no version pin) keep getting 4.6.0. Customers who want the patch on the 4.4.x line install with npm install @trigger.dev/[email protected] (or pin Docker to :release-4.4).

The version PR's "Release prep" header shows which case you're in before you merge.

Common pitfalls

  • Always cut from the tag, not main HEAD. If you cut from main, you accidentally pull in unmerged-but-pending changesets.
  • Cherry-pick the fix back to main, not the version bump. Picking the version-bump commit causes package.json conflicts and deletes the consumed changeset on main.
  • Two release branches can be active at once. release/4.4.x and release/4.5.x work concurrently. The repo-wide concurrency.group on release.yml serializes them so they don't collide on tags.
  • Stale workflow files on old release branches. A release/4.0.x cut six months ago has the .github/workflows/ from then. If you need a hotfix on a very old line, expect to update the workflows on that branch first (cherry-pick relevant CI changes from main).

Files changed

CI / scripts only. No package or app code changes.

  • release.yml — version-comparison gating, release/** triggers, propagates is_latest
  • publish.yml, publish-webapp.yml, publish-worker-v4.ymlis_latest input, gate :v4-beta, add :release-<M.m>
  • changesets-pr.yml — trigger on release/**, dynamic source-branch handling
  • scripts/enhance-release-pr.mjs — release-branch context header on the version PR

Validation

End-to-end tested in a sandbox repo with the same pipeline shape (3 npm packages, Docker image, Helm chart, cross-repo dispatch). Real npm publishes, real GHCR builds. Confirmed:

  • Lagged hotfix doesn't clobber any floating tags
  • Fresh hotfix while main has unreleased work correctly takes all floating tags
  • Two parallel release-branch hotfixes serialize without tag collisions
  • Cross-repo dispatch payload carries the right is_latest value

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 6, 2026

⚠️ No Changeset found

Latest commit: 4268187

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

Review Change Stack

Walkthrough

Workflows and a PR enhancer were updated to support release/** branches and to propagate a computed is_latest flag through the release pipeline. changesets-pr now derives SOURCE_BRANCH from the PR ref, fetches the corresponding changeset-release branch, reads the package version from that branch, and passes SOURCE_BRANCH to the enhancer. release.yml compares the new version to npm dist-tags.latest, emits is_latest and dist_tag outputs, and forwards them to downstream steps. publish workflows accept is_latest and perform semver-aware Docker tagging (adding release-MAJOR.MINOR and optionally v4-beta). scripts/enhance-release-pr.mjs obtains release context and renders a “Release prep” section in PR bodies.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Detailed summary

  • .github/workflows/changesets-pr.yml

    • Added release/** to push triggers.
    • PR handling refactored to derive SOURCE_BRANCH from GITHUB_REF_NAME, compute PR_BRANCH as changeset-release/${SOURCE_BRANCH}, fetch that branch from origin, read version from origin/${PR_BRANCH}:packages/cli-v3/package.json, and update the PR title with that version.
    • Passes SOURCE_BRANCH into the PR body enhancer via environment.
  • .github/workflows/release.yml

    • PR trigger list expanded to include release/**.
    • show-release-summary and release job conditions relaxed to accept heads starting with changeset-release/*.
    • Added outputs to expose is_latest from a new version comparison step.
    • Inserted "Compare new version to current latest" step that queries npm dist-tags.latest, compares semver, sets is_latest and dist_tag, and writes outputs for downstream steps.
    • Publish step updated to optionally publish with the computed dist_tag.
    • create-unified-release and publish-docker steps receive is_latest and use it (create-unified-release uses --latest when appropriate).
    • Dispatch changelog payload now includes is_latest.
  • .github/workflows/publish.yml

    • Added is_latest boolean input to workflow_call.inputs.
    • Threaded is_latest into publish-webapp and publish-worker-v4 calls.
  • .github/workflows/publish-webapp.yml

    • Added is_latest input to workflow_call.inputs and exposed it as INPUTS_IS_LATEST in the step environment.
    • Replaced simple tagging with semver-aware tagging: when the image tag is semver (leading v allowed), derive MAJOR.MINOR and append release-MAJOR.MINOR; if INPUTS_IS_LATEST is true, also append v4-beta.
    • Logs and emits final image_tags.
  • .github/workflows/publish-worker-v4.yml

    • Added is_latest input and passed it into steps via INPUTS_IS_LATEST.
    • Extended tagging logic to derive and append release-MAJOR.MINOR for semver tags and conditionally append v4-beta when is_latest is true.
  • scripts/enhance-release-pr.mjs

    • Added async getReleaseContext() which reads SOURCE_BRANCH, queries npm dist-tags.latest, compares semantic versions to determine willBeLatest, extracts branch/line metadata, and returns a releaseContext object.
    • Updated formatPrBody signature to accept releaseContext: formatPrBody({ version, packageEntries, serverEntries, rawBody, releaseContext }).
    • When releaseContext is present, formatPrBody renders a “Release prep” section showing version, source branch, current latest, willBeLatest, and emits hotfix guidance when the source branch matches release/*.
    • main flow updated to obtain releaseContext and pass it into formatPrBody.

No public API or exported symbol declarations were changed.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding support for release/* branches with version-comparison logic to gate floating tags.
Description check ✅ Passed The description is comprehensive and well-structured, covering the motivation, step-by-step usage guide, technical details of what changes, and validation performed, though it does not follow the repository's checklist template.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/release-branch-flow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

ericallam added 4 commits May 10, 2026 21:56
Lets us ship a patch (e.g. 4.4.6) from a release/4.4.x branch without
including unreleased work merged into main, and without the patch
clobbering floating tags incorrectly.

The release-pipeline pieces this touches and how each behaves now:

  npm dist-tag        latest if version > current latest, else release-<M.m>
  Docker :v4-beta     same gate (highest version only)
  Docker :release-X.Y new per-line floating tag, always set on a semver build
  GitHub release      --latest=true|false set explicitly (no auto-detect)

How the gate is computed:
  release.yml's 'Compare new version to current latest' step queries
  npm view @trigger.dev/sdk dist-tags.latest, compares via sort -V,
  sets is_latest=true|false. Drives every floating tag.

Triggers / refs:
  - pull_request:branches[main, release/**]
  - if-conditions allow head.ref starting with 'changeset-release/'
  - workflow_dispatch ref must be reachable from main OR a release/* branch
  - changesets-pr.yml fires on push to release/** too; PR-enhance step
    discovers source branch dynamically (no more hardcoded changeset-release/main)

Other changes:
  - gh release create: drop --target main (tag carries right commit)
  - dispatch-changelog payload includes is_latest so the marketing site
    can render lagged-line releases differently
  - enhance-release-pr.mjs prepends a Release prep header on release/*
    branches showing version, current latest, and whether the PR will
    take the latest dist-tag

release-helm.yml unchanged — already creates as draft+prerelease so it
can't claim Latest. publish-worker.yml (coordinator/provider) unchanged
since those don't have a :v4-beta-equivalent floating tag.

Validated end-to-end in ericallam/pkgring-sandbox across both scenarios:
  Scenario A (lagged hotfix): latest stays put, only release-X.Y moves
  Scenario B (main has unreleased work, hotfix is highest): latest moves
@ericallam ericallam force-pushed the chore/release-branch-flow branch from aa4a9a8 to 4268187 Compare May 10, 2026 21:34
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
scripts/enhance-release-pr.mjs (2)

436-438: ⚡ Quick win

Naive cmp may disagree with the workflow's sort -V decision.

This per-component numeric comparison breaks on prerelease versions (e.g., Number("6-beta") is NaN, and 0 || NaN propagates NaN through the reduce, leaving willBeLatest falsy regardless of order). It also under-compares when version has fewer components than currentLatest. Meanwhile, release.yml decides the actual outcome via sort -V, so the PR body header could show the opposite of what gets shipped.

Since this only affects display, it's low-risk, but unifying with semver-aware comparison avoids surprising drift between header and reality.

♻️ Replace with shell-out to `sort -V` (matches release.yml exactly) or use the `semver` package
-  const cmp = (a, b) =>
-    a.split(".").map(Number).reduce((acc, n, i) => acc || n - (b.split(".").map(Number)[i] ?? 0), 0);
-  const willBeLatest = cmp(version, currentLatest) > 0;
+  // Match release.yml's `sort -V` precisely so the PR body header never
+  // disagrees with the actual is_latest decision made at publish time.
+  const higher = await new Promise((resolve) => {
+    execFile(
+      "sh",
+      ["-c", `printf '%s\\n%s\\n' "$0" "$1" | sort -V | tail -1`, version, currentLatest],
+      { maxBuffer: 1024 * 1024 },
+      (err, stdout) => resolve(err ? "" : stdout.trim())
+    );
+  });
+  const willBeLatest = higher === version && version !== currentLatest;

Confirm whether semver is already a workspace dependency available to this script — if so, semver.gt(version, currentLatest) is even simpler.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/enhance-release-pr.mjs` around lines 436 - 438, The cmp function used
to set willBeLatest is naive and fails on prerelease and differing-component
versions; replace its logic by using a proper semver comparison (e.g., use
semver.gt(version, currentLatest) if the semver package is available in the
workspace) or, to exactly match release.yml, shell out to sort -V and compare
ordering; update the assignment to willBeLatest to call the chosen semver-aware
comparator (referencing cmp, willBeLatest, version, and currentLatest) and
remove or deprecate the old cmp implementation.

422-434: 💤 Low value

try/catch is unreachable — the inner Promise never rejects.

The Promise constructor only calls resolve (in both err and success branches), so the await cannot throw and the outer catch block is dead. Either resolve to a sentinel and check it, or reject inside the callback so the catch becomes meaningful.

♻️ Drop the dead try/catch
   let currentLatest = "0.0.0";
-  try {
-    const out = await new Promise((resolve) => {
-      execFile(
-        "npm",
-        ["view", "@trigger.dev/sdk", "dist-tags.latest"],
-        { maxBuffer: 1024 * 1024 },
-        (err, stdout) => resolve(err ? "" : stdout.trim())
-      );
-    });
-    if (out && out !== "undefined") currentLatest = out;
-  } catch {
-    // fall through with default
-  }
+  const out = await new Promise((resolve) => {
+    execFile(
+      "npm",
+      ["view", "@trigger.dev/sdk", "dist-tags.latest"],
+      { maxBuffer: 1024 * 1024 },
+      (err, stdout) => resolve(err ? "" : stdout.trim())
+    );
+  });
+  if (out && out !== "undefined") currentLatest = out;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/enhance-release-pr.mjs` around lines 422 - 434, The current try/catch
is dead because the Promise created around execFile only ever calls resolve;
update the Promise in the block that calls execFile so it accepts (resolve,
reject) and calls reject(err) when the execFile callback receives an error (and
resolve with stdout.trim() on success), which makes the outer try/catch
meaningful and preserves the existing check that assigns to currentLatest from
out; reference symbols: the Promise around execFile, the execFile callback, the
out variable, and currentLatest.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 146-149: The script currently silences npm failures and falls back
to CURRENT="0.0.0", which incorrectly treats transient registry errors as "no
latest"; change the logic around the npm view call so you capture its exit
status and output (the CURRENT variable) separately, retry or fail the job if
the npm view command exits non-zero (network/auth/5xx), and only set the "0.0.0"
default when the command succeeded but returned empty/undefined (true "no
latest" case); update the block that sets CURRENT (the npm view invocation and
the CURRENT="0.0.0" fallback) and ensure downstream logic that computes
IS_LATEST uses the distinct failure vs no-latest signal rather than treating
errors as 0.0.0.

---

Nitpick comments:
In `@scripts/enhance-release-pr.mjs`:
- Around line 436-438: The cmp function used to set willBeLatest is naive and
fails on prerelease and differing-component versions; replace its logic by using
a proper semver comparison (e.g., use semver.gt(version, currentLatest) if the
semver package is available in the workspace) or, to exactly match release.yml,
shell out to sort -V and compare ordering; update the assignment to willBeLatest
to call the chosen semver-aware comparator (referencing cmp, willBeLatest,
version, and currentLatest) and remove or deprecate the old cmp implementation.
- Around line 422-434: The current try/catch is dead because the Promise created
around execFile only ever calls resolve; update the Promise in the block that
calls execFile so it accepts (resolve, reject) and calls reject(err) when the
execFile callback receives an error (and resolve with stdout.trim() on success),
which makes the outer try/catch meaningful and preserves the existing check that
assigns to currentLatest from out; reference symbols: the Promise around
execFile, the execFile callback, the out variable, and currentLatest.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 811f672f-8055-4742-b4dd-2f2ea7bf83d6

📥 Commits

Reviewing files that changed from the base of the PR and between 6cdd881 and 4268187.

📒 Files selected for processing (6)
  • .github/workflows/changesets-pr.yml
  • .github/workflows/publish-webapp.yml
  • .github/workflows/publish-worker-v4.yml
  • .github/workflows/publish.yml
  • .github/workflows/release.yml
  • scripts/enhance-release-pr.mjs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (28)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / e2e-webapp / 🧪 E2E Tests: Webapp
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: typecheck / typecheck
🔇 Additional comments (8)
.github/workflows/publish-webapp.yml (1)

17-91: LGTM — semver-aware tagging logic is sound.

The ${TAG#v} parameter expansion safely handles both prefixed (v4.4.6) and bare (4.4.6) tags, the per-line release-MAJOR.MINOR tag is set unconditionally for semver builds, and :v4-beta is correctly gated on the workflow-level is_latest decision. Boolean→string coercion when passing through INPUTS_IS_LATEST env aligns with the bash == true comparison.

.github/workflows/publish.yml (1)

11-105: LGTM — is_latest plumbed cleanly to the v4 reusable workflows.

publish-worker (legacy v3) is intentionally not forwarded, which is consistent with :v4-beta being a v4-only tag.

.github/workflows/publish-worker-v4.yml (1)

76-97: LGTM — tag generation matches publish-webapp.yml.

Mirrors the webapp publish step's tagging logic, keeping per-line release-X.Y always-on for semver and :v4-beta gated on is_latest. Duplication with the webapp workflow is acceptable for now given the small size of the block.

.github/workflows/release.yml (4)

179-179: LGTM — conditional --tag selection is correct.

The GHA ternary (cond && truthy || falsy) cleanly switches between pnpm exec changeset publish --tag release-M.m and the default-tag form. The release- prefix avoids npm's rejection of dist-tags that parse as a semver range.


321-325: LGTM — is_latest is rendered as a raw JSON boolean.

Quoting version while leaving is_latest unquoted produces valid JSON because the upstream output is always the literal string true or false (the compare step has no if: guard and always writes the output before any downstream job consumes it). Defensive note: if a future change ever guards the compare step on a condition, the output could become empty and break the JSON payload here.


208-211: 💤 Low value

The --latest=<bool> syntax is valid and documented.

GitHub CLI's gh release create officially supports --latest=false and --latest=true for explicit control (documented at https://cli.github.com/manual/gh_release_create). The code correctly uses this syntax. Note: There are known behavioral edge cases where --latest=false may be ignored in specific scenarios (e.g., when uploading assets), though this doesn't affect the flag's basic syntax support.


76-94: 💤 Low value

The ref parameter doesn't restrict remote branches fetched by actions/checkout@v6.

With fetch-depth: 0, all remote branches (including origin/release/*) are fetched and available to git operations regardless of the ref checkout parameter. The ref parameter only controls which commit is checked out locally, not what remote refs are accessible for ancestry checks. No sparse-checkout, filter, or other restrictions are applied in the workflow or other workflows.

The suggestion to add an early if [ -z "$(...)" ] check remains valid for clarity—it would provide a clearer failure message if no release branches exist yet, though the current loop behavior (simply not iterating) is functionally correct.

			> Likely an incorrect or invalid review comment.
.github/workflows/changesets-pr.yml (1)

60-74: The code correctly leverages changesets/[email protected]'s behavior of preserving nested slashes in branch names, creating changeset-release/release/X.Y.x verbatim for a release/X.Y.x base branch. Git refs and GitHub API handle these slashes without issue.

Comment on lines +146 to +149
CURRENT=$(npm view @trigger.dev/sdk dist-tags.latest 2>/dev/null || true)
if [ -z "$CURRENT" ] || [ "$CURRENT" = "undefined" ]; then
CURRENT="0.0.0"
fi
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Major: silent npm-view failure can flip a lagged hotfix into :latest.

npm view ... 2>/dev/null || true collapses transient registry failures (network, 5xx, auth) into the same state as "no latest tag yet". Combined with the 0.0.0 default, any hotfix from release/X.Y.x becomes "newer than current latest" and IS_LATEST=true — which then promotes the hotfix to npm latest, Docker :v4-beta, and the GitHub "Latest" badge. That's the exact failure mode this whole PR is designed to prevent.

The 0.0.0 default is only safe when there genuinely is no latest (first publish). After the first release, an empty/error response should fail fast or retry rather than fall through to "becomes latest".

🛡️ Distinguish "no latest yet" from "npm unreachable"
       - name: Compare new version to current latest
         id: compare
         run: |
           set -euo pipefail
           NEW=$(jq -r '.version' packages/cli-v3/package.json)
-          CURRENT=$(npm view `@trigger.dev/sdk` dist-tags.latest 2>/dev/null || true)
-          if [ -z "$CURRENT" ] || [ "$CURRENT" = "undefined" ]; then
-            CURRENT="0.0.0"
-          fi
+          # Retry npm view to ride out transient registry blips. Only fall back
+          # to 0.0.0 on an explicit "no latest yet" response, never on errors —
+          # a silent network failure here would incorrectly promote a lagged
+          # hotfix to :latest / :v4-beta / GitHub "Latest".
+          for i in 1 2 3; do
+            if CURRENT=$(npm view `@trigger.dev/sdk` dist-tags.latest 2>&1); then
+              break
+            fi
+            echo "npm view failed (attempt $i): $CURRENT" >&2
+            CURRENT=""
+            sleep $((i * 5))
+          done
+          if [ -z "$CURRENT" ]; then
+            echo "Error: could not read `@trigger.dev/sdk` dist-tags.latest from npm." >&2
+            exit 1
+          fi
+          if [ "$CURRENT" = "undefined" ]; then
+            # Genuine "no latest dist-tag yet" — first publish.
+            CURRENT="0.0.0"
+          fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 146 - 149, The script currently
silences npm failures and falls back to CURRENT="0.0.0", which incorrectly
treats transient registry errors as "no latest"; change the logic around the npm
view call so you capture its exit status and output (the CURRENT variable)
separately, retry or fail the job if the npm view command exits non-zero
(network/auth/5xx), and only set the "0.0.0" default when the command succeeded
but returned empty/undefined (true "no latest" case); update the block that sets
CURRENT (the npm view invocation and the CURRENT="0.0.0" fallback) and ensure
downstream logic that computes IS_LATEST uses the distinct failure vs no-latest
signal rather than treating errors as 0.0.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant